# vim:ts=4:sw=4:noet:
""" Semantic Checker """

import spy
import ir
from environment import Environment

class IRProxy:
	def __init__(self, spyobj):
		self.spy = spyobj
		if isinstance(spyobj, spy.SpyClass):
			self.ivars = spyobj.instanceNames
			self.name = spyobj.name
			if isinstance(spyobj.parent, spy.SpyClass):
				self.supername = spyobj.parent.name
			else:
				self.supername = "nil"
		else:
			self.ivars = []
			self.supername = ""

class Checker:
	def __init__(self, mem, status, mcheck=True):
		self.mem = mem
		self.status = status
		self.classHash = {}
		self.messageUsage = {"boot": 1}
		self.checkValidMessages = mcheck

	def populateFromMem(self):
		for g in self.mem.globals:
			if g not in self.classHash:
				self.classHash[g] = IRProxy(self.mem.globals[g])

	def markUnused(self, classes):
		for c in classes:
			for m in c.meths:
				m.used = (m.name in self.messageUsage)

	def check(self, classes):
		for c in classes:
			self.classHash[c.name] = c
		self.checked = {}
		for c in classes:
			self.checkClass(c)

	def checkClass(self, cls):
		# check if we've already checked this class
		if cls.name in self.checked:
			return
		self.checked[cls.name] = True
		# find and set the superclass
		if cls.supername not in self.classHash:
			supercls = self.mem.getGlobal(cls.supername)
			if not supercls:
				self.status.error(
						"Couldn't find superclass '%s' for class '%s'" % (
						cls.supername, cls.name))
		# try get the spyclass for this class
		spyobj = self.mem.getGlobal(cls.name)
		if not spyobj:
			supercls = self.mem.getGlobal(cls.supername)
			if not supercls:
				self.checkClass(self.classHash[cls.supername])
			spyobj = spy.SpyClass(self.mem, cls.name, supercls)
			self.mem.addGlobal(cls.name, spyobj)
		# try get the metaclass and check it
		if not spyobj.cls:
			if cls.name[:4]=="Meta":
				if not self.mem.getGlobal("Class"):
					self.checkClass(self.classHash["Class"])
				spyobj.cls = self.mem.getGlobal("Class")
			else:
				self.checkClass(self.classHash["Meta"+cls.name])
				spyobj.cls = self.mem.getGlobal("Meta"+cls.name)
		# check superclass
		if not spyobj.parent:
			supercls = self.mem.getGlobal(cls.supername)
			if not supercls:
				self.checkClass(self.classHash[cls.supername])
			spyobj.parent = supercls
		# setup instance variables
		curcls = cls
		ivarnames = []
		theseIvars = []
		for iv in curcls.ivars:
			theseIvars.append(iv)
		while curcls:
			for i,iv in enumerate(curcls.ivars):
				ivarnames.insert(i,iv)
			if curcls.supername in self.classHash:
				curcls = self.classHash[curcls.supername]
			else:
				break
		spyobj.instanceNames = theseIvars
		spyobj.instanceSize = len(ivarnames)
		spyobj.docs = cls.docs
		spyobj.pragmas = cls.pragmas
		# setup base environment
		self.clsEnv = Environment(self, ivarnames, None, Environment.IVAR, cls.name)
		# create method dictionary
		if not spyobj.methods:
			spyobj.methods = spy.SpyDictionary(self.mem)
		# check methods
		for m in cls.meths:
			self.checkMethod(cls, spyobj, m)
		cls.spy = spyobj

	def checkSingleMethod(self, spycls, meth):
		self.clsEnv = Environment(self, spycls.getAllIvars(), None, Environment.IVAR, spycls.name)
		self.checkMethod(None, spycls, meth)

	def checkMethod(self, cls, spycls, meth):
		if spycls.hasMethod(meth.name):
			spymeth = spycls.methods.get(meth.name)
			spymeth.sig = meth.getSig()
			spymeth.args = meth.args
			spymeth.source = meth.source
			# A recompiled method loses the docs, so set the IRMethod's docs from 
			# the old SpyMethod
			if meth.docs=="":
				meth.docs = spymeth.docs
		else:
			spymeth = spy.SpyMethod(self.mem, meth.name, spycls, meth.source)
			spymeth.sig = meth.getSig()
			spymeth.args = meth.args
			if len(spycls.methods)>50:
				self.status.error("Class '%s' has too many methods" % spycls.name)
			spycls.methods.add(meth.name, spymeth)
		spymeth.docs = meth.docs
		temps = []
		temps.extend(meth.args)
		temps.extend(meth.temps)
		self.curEnv = Environment(self, temps, self.clsEnv, Environment.METH_TEMP, meth.name)
		self.tempSize = len(temps)
		self.tempMax = self.tempSize
		for st in meth.body.statements:
			self.checkExpression(meth, st)
		meth.spy = spymeth
		meth.maxTemps = self.tempMax

	def checkExpression(self, m, st):
		if isinstance(st, ir.IRReturn):      self.checkReturn(m, st)
		elif isinstance(st, ir.IRAssign):    self.checkAssign(m, st)
		elif isinstance(st, ir.IRMessage):   self.checkMessage(m, st)
		elif isinstance(st, ir.IRPrimitive): self.checkPrimitive(m, st)
		elif isinstance(st, ir.IRName):      self.checkName(m, st)
		elif isinstance(st, ir.IRCascade):   self.checkCascade(m, st)
		elif isinstance(st, ir.IRInteger):   self.checkInteger(m, st)
		elif isinstance(st, ir.IRString):    self.checkString(m, st)
		elif isinstance(st, ir.IRSymbol):    self.checkSymbol(m, st)
		elif isinstance(st, ir.IRBlock):     self.checkBlock(m, st)
		elif isinstance(st, ir.IRChar):      self.checkChar(m, st)
		elif isinstance(st, ir.IRArray):     self.checkArray(m, st)
		elif st==None:
			pass
		else:
			self.status.error("ir node not understood: %s" % st)
	
	def checkReturn(self, m, st):
		self.checkExpression(m, st.value)
	
	def checkAssign(self, m, st):
		self.checkExpression(m, st.var)
		self.checkExpression(m, st.value)
	
	def checkMessage(self, m, st):
		if (self.checkValidMessages and self.mem.messageTable and 
			(st.name not in self.mem.messageTable)):
			self.status.error("No class responds to '%s'" % st.name, st.line)
		if st.name in self.messageUsage:
			self.messageUsage[st.name] += 1
		else:
			self.messageUsage[st.name] = 1
		self.checkExpression(m, st.recv)
		for a in st.args:
			self.checkExpression(m, a)
	
	def checkBlock(self, m, st):
		st.tempStart = self.tempSize
		self.tempStart = st.tempStart
		self.tempSize += len(st.args)
		if self.tempSize > self.tempMax:
			self.tempMax = self.tempSize
		self.curEnv = Environment(self, st.args, self.curEnv, Environment.BLOCK_ARG, m.name, st.tempStart)
		for bst in st.statements:
			self.checkExpression(m, bst)
		self.curEnv = self.curEnv.parent
		self.tempSize -= len(st.args)

	def checkName(self, m, st):
		if st.checked:
			return
		st.checked = True
		if st.name == "super":
			st.type = Environment.METH_TEMP
			st.pos = -1
			return
		ne = self.curEnv.has(st.name)
		if ne:
			st.type = ne.type
			st.pos = ne.get(st.name)
			if st.type == Environment.BLOCK_ARG:
				st.pos += ne.varBase
		else:
			st.type = Environment.GLOBAL
			st.pos = 0 
			if (st.name!="Smalltalk") and (st.name not in self.classHash):
				if not self.mem.getGlobal(st.name):
					self.status.error("Couldn't find variable '%s'" % st.name, st.line)
	
	def checkCascade(self, m, st):
		self.checkExpression(m, st.node)
		for stc in st.list:
			self.checkExpression(m, stc)
	
	def checkPrimitive(self, m, st):
		for stp in st.args:
			self.checkExpression(m, stp)

	def checkInteger(self, m, st):
		pass
	
	def checkString(self, m, st):
		pass
	
	def checkSymbol(self, m, st):
		pass
	
	def checkChar(self, m, st):
		pass

	def checkArray(self, m, st):
		for v in st.values:
			self.checkExpression(m, v)

